<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Mytest extends CI_Controller
{

	protected $flag = '';
	protected $pids = [];
	protected $process_num = 2;
	protected $is_set_signg_handler = false;


	function __construct()
	{
		// global $STDOUT, $STDERR;
		// fclose(STDOUT);
		// fclose(STDERR);

		// $log = APPPATH . '/logs/ci-pcntl.log';
		// $STDOUT = fopen($log, "rw+");
		// $STDOUT = $stdout_handler;
		// $STDERR = $stdout_handler;

		pcntl_async_signals(true);

		parent::__construct();

		set_error_handler(function ($errno, $errstr, $errfile, $errline) {
			throw new Exception($errstr, $errno);
		});

		set_exception_handler(function (\Throwable $ex) {
			$msg = sprintf('%s at %s line %s', $ex->getMessage(), $ex->getFile(), $ex->getLine());
			self::writeln($msg);
		});

		register_shutdown_function(function () {
			// self::writeln(__METHOD__ . ' @' . getmypid());
			//删除pid
			$file = FCPATH . '/pid';
			// echo file_exists($file) ? 'A' : 'B';
			// file_exists($file) && unlink($file);
		});
	}


	static function writeln($str)
	{
		echo $str . PHP_EOL;
	}

	function check_is_master()
	{
		$pid = file_get_contents('./pid');
		return getmypid() == $pid;
	}

	function set_system_signal_handler()
	{

		//平滑退出
		pcntl_signal(SIGTERM, [$this, 'signal_handle_term']);
		//平滑重启
		pcntl_signal(SIGUSR2, [$this, 'signal_handle_usr2']);
		//ctrl+c
		pcntl_signal(SIGINT, [$this, 'signal_handle_term']);

		// pcntl_signal(SIGCLD, [$this, 'signal_handle_cld']);
		pcntl_signal(SIGCHLD, [$this, 'signal_handle_cld']);

		$this->is_set_signg_handler = true;
		self::writeln('set signg handler ok');
	}

	function signal_handle_usr2()
	{
		if ($this->check_is_master()) {
			//结束所以子进程
			foreach ($this->pids as $pid) {
				posix_kill($pid, SIGTERM);
			}

			exit("exit master process" . PHP_EOL);
		} else {
			$this->reload_worker();
		}
	}

	function signal_handle_term()
	{
		if ($this->check_is_master()) {
			if (empty($this->pids)) exit();

			//结束所有子进程
			foreach ($this->pids as $pid) {
				self::writeln("set worker:$pid term signal");
				posix_kill($pid, SIGTERM);
				unset($this->pids[$pid]);
			}
		}

		$this->flag = "exit";
	}

	function child_term()
	{
		self::writeln("work:" . getmypid() . " ready to exit!");
		$this->flag = "exit";
	}

	function signal_handle_cld()
	{
		while (($pid = pcntl_wait($status)) > 0) {
			self::writeln("SIGCLD:$pid");
			unset($this->pids[$pid]);
			self::writeln(json_encode(array_keys($this->pids)));
		}
	}

	function reload_worker()
	{
		//退出worker,后生成一个新的worker
		self::writeln('子进程退出');
		//TODO::通过管道写入新生成一个work指令

		//当前进程退出
		// $this->flag = 'exit';
	}


	function get_pid_from_file()
	{
		$file = FCPATH . '/pid';
		if (!file_exists($file)) throw new \Exception('pid 文件不存在', 10000);
		if (!is_readable($file)) throw new \Exception('pid 文件不可读');

		return file_get_contents($file);
	}

	function set_pid_file()
	{
		$pid = posix_getpid();
		$file = FCPATH . '/pid';
		// if (!is_writable($file)) throw new \Exception('pid文件不可以写');
		file_put_contents($file, $pid);
	}

	function check_is_running()
	{
		try {
			$pid = $this->get_pid_from_file();
		} catch (\Throwable $ex) {
			//文件不存在
			if ($ex->getCode() != 10000) {
				throw $ex;
			} else {
				return;
			}
		}

		if (posix_kill($pid, 0)) {
			echo 'another process is running.........';
			exit();
		}
	}

	function index()
	{
		// $file = APPPATH.'logs/ci-pcntl.log';
		// if(!file_exists($file)){
		// 	touch($file);
		// }
		// fclose(STDOUT);
		// $STDOUT=fopen($file,'rw');
		// echo time();
		// exit();
		//ps -ef|grep yibai|grep -v grep |awk '{print $2}'|xargs kill -9

		$args = func_get_args() ?? [''];
		$command = $args[0] ?? '';
		cli_set_process_title('yibai_jobs:master process');

		switch ($command) {
			case 'job:start':
				$this->job_start();
				break;
			case 'job:stop':
				$this->job_stop();
				break;
			case 'job:reload':
				$this->job_reload();
				break;
			default:
				self::writeln("usage:job:start | jobs:stop | job:reload");
				exit();
				break;
		}
	}

	function job_start()
	{
		// pcntl_exec("ps -ef|grep yibai|grep -v grep |awk '{print $2}'|xargs kill -9");
		$this->check_is_running();

		$pid = pcntl_fork();
		if ($pid < 0) throw new \Exception('deamon fork failure!');

		if ($pid > 0) {
			//父进程直接退出,子进程由1号进程接管
			exit(0);
		} else {
			/*
            主要目的脱离终端控制，自立门户。
            创建一个新的会话，而且让这个pid统治这个会话，他既是会话组长，也是进程组长。
            而且谁也没法控制这个会话，除了这个pid。当然关机除外。。
            这时可以成做pid为这个无终端的会话组长。
            注意这个函数需要当前进程不是父进程，或者说不是会话组长。
            在这里当然不是，因为父进程已经被kill
            */
			$sid = posix_setsid();
			if ($sid < 0) {
				throw new \Exception('setsid error');
				exit;
			}

			self::writeln('master process:' . getmypid());
			$this->set_pid_file();
			cli_set_process_title('yibai_jobs:master process');
			$this->create_workers($this->process_num);

			self::writeln('start ok:');
			self::writeln(json_encode($this->pids));

			while (true) {
				if (empty($this->pids) && $this->flag == "exit") {
					self::writeln('all works are killed,master exit...');
					exit();
				}
				sleep(1);
				// self::writeln(json_encode($this->pids));
			}
		}
	}

	function job_stop()
	{
		$pid = $this->get_pid_from_file();
		posix_kill($pid, SIGTERM);
	}

	function job_reload()
	{
		posix_kill(getmypid(), SIGUSR2);
	}

	function create_workers($worker_num)
	{
		for ($i = 0; $i < $this->process_num; $i++) {
			$pid = pcntl_fork();
			if ($pid < 0) echo __METHOD__ . ' failure';

			if ($pid == 0) {
				$this->init_worker();
			} else {
				$this->pids[$pid] = $pid;
				if (!$this->is_set_signg_handler) {
					$this->set_system_signal_handler();
				}
			}
		}
	}

	function init_worker()
	{
		cli_set_process_title('yibai_jobs:worker process');
		self::writeln('i am worker process:' . getmypid());
		// $this->set_system_signal_handler();
		$this->worker_process();
	}

	/**
	 * /cron/my_test/process  
	 */
	function worker_process()
	{
		pcntl_signal(SIGTERM, [$this, 'child_term']);

		while (true) {
			try {
				$ch = curl_init('http://127.0.0.1:9001/');
				curl_exec($ch);
				curl_close($ch);
				//业务逻辑
				// echo 1 / 0;
				// throw new \Exception('customer exception');
			} catch (\Throwable $ex) {
				echo sprintf('%s %s发生异常： %s %s', PHP_EOL, getmypid(), $ex->getMessage(), PHP_EOL);
			}

			if ($this->flag == 'exit') {
				self::writeln('worker:' . getmypid() . ' ready to exit');
				// sleep(1);
				exit();
			}
			sleep(1);
		}
	}
}
